package cn.kkdlk.transform;

import org.locationtech.proj4j.*;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class CoordinateTransformUtils {

    private static CRSFactory mCrsFactory = new CRSFactory();

    /**
     * wgs84 投影参数
     */
    private static String WGS84_CRS_PARAM = "+proj=longlat +datum=WGS84 +no_defs";
    /**
     * cgcs2000 不带带号投影参数
     */
    private static String CGCS2000_NO_SIGNED_CRS_PARAM = "+proj=tmerc +lat_0=0 +lon_0=%s +k=1 +x_0=500000 +y_0=0 +ellps=GRS80 +units=m +no_defs";
    /**
     * cgcs2000 带带号投影参数
     */
    private static String CGCS2000_SIGNED_CRS_PARAM = "+proj=tmerc +lat_0=0 +lon_0=%s +k=1 +x_0=%s500000 +y_0=0 +ellps=GRS80 +units=m +no_defs";

    private CoordinateTransformUtils() {
    }

    /**
     * 创建坐标参考系统
     */
    private static CoordinateReferenceSystem createCRS(String crsName, String param) {
        return mCrsFactory.createFromParameters(crsName, param);
    }

    /**
     * 创建坐标转换器
     */
    private static CoordinateTransform createTransform(CoordinateReferenceSystem sourceCRS, CoordinateReferenceSystem targetCRS) {
        CoordinateTransformFactory ctf = new CoordinateTransformFactory();
        return ctf.createTransform(sourceCRS, targetCRS);
    }


    /**
     * 根据带宽,wgs84经度，计算中央子午线
     *
     * @param longitude wgs 84 经度
     * @param bandWidth 带宽[3,6]
     * @return 中央子午线
     */
    private static int generateCentralMeridianByLng(double longitude, int bandWidth) {
        if (bandWidth == 3) {
            return (int) Math.round(longitude / bandWidth) * bandWidth;
        } else if (bandWidth == 6) {
            // 带号
            int signed = mathL0AndBandWidth2Signed((int) longitude, bandWidth);
            // 中央经线
            return mathSignedAndBandWidth2L0(signed, bandWidth);
        } else {
            throw new RuntimeException("带号不在带宽【3,6】的范围内");
        }
    }

    /**
     * 根据带宽和带号计算中央经线
     *
     * @param signed    带号
     * @param bandWidth 带宽[3,6]
     * @return 中央经线
     */
    private static int mathSignedAndBandWidth2L0(int signed, int bandWidth) {
        // 3度带是 带号乘以带宽， 6度带是 带号乘以带宽-3
        /*if (bandWidth == 3) {
            return bandWidth * signed;
        } else if (bandWidth == 6) {
            return bandWidth * signed - 3;
        } else {
            throw new BusinessException("带号不在带宽【3,6】的范围内");
        }*/
        return bandWidth * signed - (bandWidth - 3);
    }

    /**
     * 根据中央子午线和带宽计算带号
     *
     * @param L0        中央子午线
     * @param bandWidth 带宽
     * @return 带号
     */
    private static int mathL0AndBandWidth2Signed(int L0, int bandWidth) {
        // double bandNum = 0;
        // if ("三度带".equals(step)) {
        //     bandNum = L0 / 3;
        // } else if ("六度带".equals(step)) {
        //     bandNum = (L0 + 3) / 6;
        // }
        if (bandWidth == 3) {
            // 四舍五入
            return Math.round((float) (L0 + (bandWidth - 3)) / bandWidth);
        } else if (bandWidth == 6) {
            // 取整
            return (L0 + (bandWidth - 3)) / bandWidth;
        } else {
            throw new RuntimeException("带号不在带宽【3,6】的范围内");
        }
    }

    /**
     * 格格式化CGCS2000的X坐标，获取整数部分
     *
     * @param x
     * @return
     */
    private static String formatCGCS2000ToIntegerX(double x) {
        BigDecimal integerX = new BigDecimal(x).setScale(0, RoundingMode.DOWN);
        return integerX.toPlainString();
    }

    /**
     * 执行坐标转换
     */
    private static ProjCoordinate transformCoordinates(ProjCoordinate sourceCoordinate, CoordinateReferenceSystem sourceCRS, CoordinateReferenceSystem targetCRS) {
        CoordinateTransform transform = createTransform(sourceCRS, targetCRS);
        ProjCoordinate targetCoordinate = new ProjCoordinate(0.0, 0.0, 0.0);
        transform.transform(sourceCoordinate, targetCoordinate);
        targetCoordinate.z = sourceCoordinate.z;
        return targetCoordinate;
    }

    // =================================================================================


    /**
     * 坐标cgcs2000 转 wgs84
     *
     * @param sourceCoordinate 源坐标
     * @param L0               中央经线
     * @param bandWidth        带宽 3 6
     * @return
     */
    public static ProjCoordinate cgcs2000ToWGS84ByL0BandWidth(ProjCoordinate sourceCoordinate, int L0, int bandWidth) {
        // 根据中央经线和带宽计算带号
        int signed = mathL0AndBandWidth2Signed(L0, bandWidth);
        // 格式化CGCS2000的X坐标，获取整数部分
        String cgcs2000XStr = formatCGCS2000ToIntegerX(sourceCoordinate.x);
        // 是否携带带号  没带带号是6位带了是8位
        boolean hasSigned = cgcs2000XStr.length() >= 8 && cgcs2000XStr.startsWith(String.valueOf(signed));
        if (hasSigned) {
//            log.info("当前X坐标携带带号，解析出带号为：{}", signed);
        }
        // 根据是否携带带号给不同的参数
        String param = hasSigned ?
                String.format(CGCS2000_SIGNED_CRS_PARAM, L0, signed) :
                String.format(CGCS2000_NO_SIGNED_CRS_PARAM, L0);
        CoordinateReferenceSystem sourceCRS = createCRS("CGCS2000L0" + (hasSigned ? "Signed" : "NoSigned"), param);
        CoordinateReferenceSystem targetCRS = createCRS("WGS84", WGS84_CRS_PARAM);
        return transformCoordinates(sourceCoordinate, sourceCRS, targetCRS);
    }

    /**
     * 已知带号和带宽，坐标cgcs2000 转 wgs84 【带带号】
     *
     * @param sourceCoordinate 源坐标
     * @param signed           带号
     * @param bandWidth        带宽
     * @return
     */
    public static ProjCoordinate cgcs2000ToWGS84BySingedBandWidth(ProjCoordinate sourceCoordinate, int signed, int bandWidth) {
        // 中央经线
        int L0 = mathSignedAndBandWidth2L0(signed, bandWidth);
        // 格式化CGCS2000的X坐标，获取整数部分
        String cgcs2000XStr = formatCGCS2000ToIntegerX(sourceCoordinate.x);
        // 是否携带带号  没带带号是6位带了是8位
        boolean hasSigned = cgcs2000XStr.length() >= 8 && cgcs2000XStr.startsWith(String.valueOf(signed));
        if (hasSigned) {
//            log.info("当前X坐标携带带号，解析出带号为：{}", signed);
        }
        // 根据是否携带带号给不同的参数
        String param = hasSigned ?
                String.format(CGCS2000_SIGNED_CRS_PARAM, L0, signed) :
                String.format(CGCS2000_NO_SIGNED_CRS_PARAM, L0);
        CoordinateReferenceSystem sourceCRS = createCRS("CGCS2000L0" + (hasSigned ? "Signed" : "NoSigned"), param);
        CoordinateReferenceSystem targetCRS = createCRS("WGS84", WGS84_CRS_PARAM);

        return transformCoordinates(sourceCoordinate, sourceCRS, targetCRS);
    }

    // =================================================================================

    /**
     * wgs84转cgcs2000, 2000坐标【不要带号】
     *
     * @param sourceCoordinate 源坐标
     * @param signed           带号
     * @return 2000坐标不要带号
     */
    public static ProjCoordinate wgs84ToCGCS2000NoSignedBySigned(ProjCoordinate sourceCoordinate, int signed) {
        // 根据带号创建2000坐标的转换参数
        CoordinateReferenceSystem targetCRS = null;
        if (signed >= 24 && signed <= 45) {
            //三度带
            int L0 = mathSignedAndBandWidth2L0(signed, 3);
            String param = String.format(CGCS2000_NO_SIGNED_CRS_PARAM, L0);
            targetCRS = createCRS("CGCS2000NoSigned", param);
        } else if (signed >= 13 && signed <= 23) {
            //六度带
            int L0 = mathSignedAndBandWidth2L0(signed, 6);
            String param = String.format(CGCS2000_NO_SIGNED_CRS_PARAM, L0);
            targetCRS = createCRS("CGCS2000NoSigned", param);
        } else {
//            log.error("带号错误，不在中国范围内，无法得知其分带,错误分带为： {}", signed);
            throw new RuntimeException("带号错误，不在中国范围内，无法得知其分带");
        }
        CoordinateReferenceSystem sourceCRS = createCRS("WGS84", WGS84_CRS_PARAM);
        return transformCoordinates(sourceCoordinate, sourceCRS, targetCRS);
    }

    /**
     * wgs84转cgcs2000, 2000坐标【带带号】
     *
     * @param sourceCoordinate 源坐标
     * @param signed           带号
     * @return 2000坐标带带号
     */
    public static ProjCoordinate wgs84ToCGCS2000SignedBySigned(ProjCoordinate sourceCoordinate, int signed) {
        // 根据带号创建2000坐标的转换参数
        CoordinateReferenceSystem targetCRS = null;
        if (signed >= 24 && signed <= 45) {
            //三度带
            int L0 = mathSignedAndBandWidth2L0(signed, 3);
            String param = String.format(CGCS2000_SIGNED_CRS_PARAM, L0);
            targetCRS = createCRS("CGCS2000Signed", param);
        } else if (signed >= 13 && signed <= 23) {
            //六度带
            int L0 = mathSignedAndBandWidth2L0(signed, 6);
            String param = String.format(CGCS2000_SIGNED_CRS_PARAM, L0);
            targetCRS = createCRS("CGCS2000Signed", param);
        } else {
//            log.error("带号错误，不在中国范围内，无法得知其分带,错误分带为： {}", signed);
            throw new RuntimeException("带号错误，不在中国范围内，无法得知其分带");
        }
        CoordinateReferenceSystem sourceCRS = createCRS("WGS84", WGS84_CRS_PARAM);
        return transformCoordinates(sourceCoordinate, sourceCRS, targetCRS);
    }


    /**
     * 已知84坐标将他转为CGCS2000并限制2000坐标参数（中央经线和带宽）【不要带号】
     *
     * @param sourceCoordinate 源坐标
     * @param L0               中央经线
     * @param bandWidth        带宽
     * @return 不要带号的2000
     */
    public static ProjCoordinate wgs84ToCGCS2000NoSignedByL0AndBandWidth(ProjCoordinate sourceCoordinate, int L0, int bandWidth) {
//        log.info("中央经线:{};带宽:{}", L0, bandWidth);
        CoordinateReferenceSystem sourceCRS = createCRS("WGS84", WGS84_CRS_PARAM);
        CoordinateReferenceSystem targetCRS = createCRS("CGCS2000NoSigned", String.format(CGCS2000_NO_SIGNED_CRS_PARAM, L0));
        return transformCoordinates(sourceCoordinate, sourceCRS, targetCRS);
    }

    /**
     * 已知84坐标将他转为CGCS2000并限制2000坐标参数（中央经线和带宽）【要带号】
     *
     * @param sourceCoordinate 源坐标
     * @param L0               中央经线
     * @param bandWidth        带宽
     * @return 要带号的2000
     */
    public static ProjCoordinate wgs84ToCGCS2000SignedByL0AndBandWidth(ProjCoordinate sourceCoordinate, int L0, int bandWidth) {
        int signed = mathL0AndBandWidth2Signed(L0, bandWidth);
//        log.info("中央经线:{};分带号:{};带宽:{}", L0, signed, bandWidth);
        CoordinateReferenceSystem sourceCRS = createCRS("WGS84", WGS84_CRS_PARAM);
        CoordinateReferenceSystem targetCRS = createCRS("CGCS2000Signed", String.format(CGCS2000_SIGNED_CRS_PARAM, L0, signed));
        return transformCoordinates(sourceCoordinate, sourceCRS, targetCRS);
    }


    /***
     * WGS84转CGCS2000，根据带宽和wgs84经纬度转换为cgcs2000 【带带号】
     * @param sourceCoordinate 源坐标
     * @param bandWidth 带宽【3 或 6】
     * @return 带带号
     */
    public static ProjCoordinate wgs84ToCGCS2000Signed(ProjCoordinate sourceCoordinate, int bandWidth) {
        // 获取wgs84经度
        double lon = sourceCoordinate.x;
        //计算中央经线
        int L0 = generateCentralMeridianByLng(lon, bandWidth);
        //计算分带号
        int signed = mathL0AndBandWidth2Signed(L0, bandWidth);
//        log.info("中央经线:{};分带号:{}", L0, signed);
        CoordinateReferenceSystem sourceCRS = createCRS("WGS84", WGS84_CRS_PARAM);
        CoordinateReferenceSystem targetCRS = createCRS("CGCS2000Signed", String.format(CGCS2000_SIGNED_CRS_PARAM, L0, signed));
        return transformCoordinates(sourceCoordinate, sourceCRS, targetCRS);
    }


    /***
     * WGS84转CGCS2000，根据带宽和wgs84经纬度转换为cgcs2000，不带带号
     * @param sourceCoordinate 源坐标
     * @param bandWidth 带宽【3 或 6】
     * @return
     */
    public static ProjCoordinate wgs84ToCGCS2000NoSigned(ProjCoordinate sourceCoordinate, int bandWidth) {
        // 获取wgs84经度
        double lon = sourceCoordinate.x;
        //计算中央经线
        int L0 = generateCentralMeridianByLng(lon, bandWidth);
        CoordinateReferenceSystem sourceCRS = createCRS("WGS84", WGS84_CRS_PARAM);
        CoordinateReferenceSystem targetCRS = createCRS("CGCS2000Signed", String.format(CGCS2000_NO_SIGNED_CRS_PARAM, L0));
        return transformCoordinates(sourceCoordinate, sourceCRS, targetCRS);
    }


    public static void main(String[] args) {
        ProjCoordinate sourceCoordinate = new ProjCoordinate();
        sourceCoordinate.x = 35433615.175697;
        sourceCoordinate.y = 3517073.194442;
        int bandWidth = 3;
//        ProjCoordinate projCoordinate = wgs84ToCGCS2000(sourceCoordinate, bandWidth);
//        log.info("cgcs2000坐标: {},{}", String.format("%.6f", projCoordinate.x), String.format("%.6f", projCoordinate.y));
        ProjCoordinate wgs84 = cgcs2000ToWGS84ByL0BandWidth(sourceCoordinate, 105, bandWidth);
        System.out.println(wgs84.x + "," + wgs84.y);
//        log.info("wgs84坐标: {},{}", wgs84.x, wgs84.y);
//        ProjCoordinate projCoordinate1 = cgcs2000ToWGS84BySingedBandWidth(sourceCoordinate, 36, bandWidth);
//        log.info("转换2000：{},{}", projCoordinate1.x, projCoordinate1.y);
    }

//    中央经线:105;分带号:35  104.2991809630623 31.774603674536436
// cgcs2000坐标: 35433615.175697,3517073.194442
}
